package midend.IR_Optimizer;

import midend.*;
import midend.IR_Type.IR_Const;
import midend.IR_Type.IR_Float;
import midend.IR_Type.IR_Int32;
import midend.IR_Module;
import midend.IR_Builder;
import midend.IR_Instruction.*;
import midend.IR_Type.IR_Type;
import midend.IR_Value.*;

import java.util.*;

public class GVN {
    private IR_Module module;
    private HashMap<String, Instruction> GVNMap;  // 记录已经处理过的指令的键和值
    private IR_Builder builder = new IR_Builder(); // 不一定非要用

    public GVN(IR_Module module) {
        this.module = module;
    }

    public void Optimize() {
        for (IR_FuncBlock function : module.getFuncBlocks()) {
            IR_BaseBlock entry = function.get_bb_List().get(0);
            GVNMap = new HashMap<>();
            GVNvisit(entry);
        }
    }

    public void GVNvisit(IR_BaseBlock block) {
        // AluOptimize(block);
        HashSet<Instruction> inserted = new HashSet<>();  // 用于记录当前block有哪些指令是“首次出现”，并被加入了全局值编号表
        LinkedList<Instruction> instructions = block.get_Instruction_List();
        LinkedList<Instruction> temp = new LinkedList<>(instructions);
        for (Instruction instruction : temp) {
            if (canBeGVN(instruction)) {
                if (repeated(instruction)) {  // 在repeated函数中，进行相同值替代操作
                    inserted.add(instruction);
                }
            }
        }

        for (IR_BaseBlock basicBlock : block.getChildBlock()) {
            GVNvisit(basicBlock);
        }

        // 清理当前基本块（BasicBlock）的指令记录，确保 GVNMap 不会跨基本块泄露冗余信息
        for (Instruction instruction : inserted) {
            if ((instruction instanceof Calc_Instruction || instruction instanceof Cond_Instruction) && isSwap(instruction)) {
                GVNMap.remove(getSwapHash(instruction));
            }
            GVNMap.remove(getHash(instruction));
        }
    }


    //检查值是否为常量
    private boolean is_Const(IR_Value value) {
        return value.get_Const_Type() == IR_Const.IR_ConstInt32 ||
                value.get_Const_Type() == IR_Const.IR_ConstFloat || value.get_Const_Type() == IR_Const.IR_ConstInt1;
    }

    public boolean repeated(Instruction instruction) {
        if (instruction instanceof Call_Instruction || instruction instanceof Pointer_Instruction || instruction instanceof Zext_Instruction) {
            String hash = getHash(instruction);
            if (GVNMap.containsKey(hash)) {
                IR_Value oldValue = instruction.get_op().get(0);
                IR_Value newValue = GVNMap.get(hash).get_op().get(0);
                module.replaceUse(oldValue, newValue);
                instruction.remove();
                return false;
            }
            GVNMap.put(hash, instruction);
            return true;
        } else if (instruction instanceof Calc_Instruction || instruction instanceof Cond_Instruction) {
            String hash = getHash(instruction);
            if (GVNMap.containsKey(hash)) {
                IR_Value oldValue = instruction.get_op().get(0);
                IR_Value newValue = GVNMap.get(hash).get_op().get(0);
                module.replaceUse(oldValue, newValue);
                instruction.remove();
                return false;
            }
            if (isSwap(instruction)) {
                String swapHash = getSwapHash(instruction);
                if (GVNMap.containsKey(swapHash)) {
                    IR_Value oldValue = instruction.get_op().get(0);
                    IR_Value newValue = GVNMap.get(hash).get_op().get(0);
                    module.replaceUse(oldValue, newValue);
                    instruction.remove();
                    return false;
                }
                GVNMap.put(swapHash, instruction);
            }
            GVNMap.put(hash, instruction);
            return true;
        }
        throw new RuntimeException("wrong instruction type");
    }



    public String getSwapHash(Instruction instruction) {
        if (instruction instanceof Cond_Instruction) {
            int op = ((Cond_Instruction) instruction).get_cond();
            String op_name = (op>=0 && op<=5) ? IR_Const.int_Compare[op] : IR_Const.float_Compare[op-6];
            return ((Cond_Instruction) instruction).get_op().get(2).get_ValueName() + op + ((Cond_Instruction) instruction).get_op().get(1).get_ValueName();
        }
        else {
            String cal_op = ((Calc_Instruction) instruction).get_op_Type();
            return ((Calc_Instruction) instruction).get_op().get(2).get_ValueName() + cal_op + ((Calc_Instruction) instruction).get_op().get(1).get_ValueName();
//
        }
    }

    public String getHash(Instruction instruction) {
        if (instruction instanceof Pointer_Instruction) {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(((Pointer_Instruction) instruction).get_base().get_ValueName());
            List<IR_Value> values_origin = ((Pointer_Instruction) instruction).get_list();
            ArrayList<IR_Value> values = new ArrayList<>(values_origin);
            for (int count = 0; count < values.size(); count++) {
                stringBuilder.append("[").append(values.get(count).get_ValueName()).append("]");
            }
            return stringBuilder.toString();
        } else if (instruction instanceof Call_Instruction) {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(((Call_Instruction) instruction).get_funcBlock().get_FuncName()).append("(");
            List<IR_Value> params_origin = ((Call_Instruction) instruction).get_params();
            ArrayList<IR_Value> params = new ArrayList<>(params_origin);
            for (int count = 0; count < params.size(); count++) {
                stringBuilder.append(params.get(count).get_ValueName());
                if (count != params.size() - 1) {
                    stringBuilder.append(", ");
                }
            }
            stringBuilder.append(")");
            return stringBuilder.toString();
        } else if (instruction instanceof Zext_Instruction) {
            return instruction.toString();
        } else if (instruction instanceof Calc_Instruction) {
            String op = ((Calc_Instruction) instruction).get_op_Type();
//            if (instruction instanceof Cond_Instruction) {
//                op = ((Cond_Instruction) instruction).get_op().toString();
            return ((Calc_Instruction) instruction).get_op().get(1).get_ValueName() + op + ((Calc_Instruction) instruction).get_op().get(2).get_ValueName();
//            }
        }
        else if (instruction instanceof Cond_Instruction) {
            int op = ((Cond_Instruction) instruction).get_cond();
            String op_name = (op>=0 && op<=5) ? IR_Const.int_Compare[op] : IR_Const.float_Compare[op-6];
            return ((Cond_Instruction) instruction).get_op().get(1).get_ValueName() + op + ((Cond_Instruction) instruction).get_op().get(2).get_ValueName();
        }
        throw new RuntimeException("wrong Instruction type");
    }

    public boolean canBeGVN(Instruction instruction) {
        // 这样的几个类型的指令没有副作用，可以进行GVN优化
        if (instruction instanceof Calc_Instruction || instruction instanceof Zext_Instruction || instruction instanceof Pointer_Instruction) {
            return true;
        }
        if (instruction instanceof Call_Instruction) {
            IR_FuncBlock function = ((Call_Instruction) instruction).get_funcBlock();
            return !function.isLib() && function.canBeGVN(); // 能够进行GVN的函数必须是纯函数，没有在函数内调用其他函数，也没有传地址引用
        }
        return false;
    }

    public boolean isSwap(Instruction instruction) {
        // 判断指令是否满足交换律

        if (instruction instanceof Cond_Instruction) {
            int op = ((Cond_Instruction) instruction).get_cond();
            return op == IR_Const.IR_EQ || op == IR_Const.IR_NE || op == IR_Const.IR_UEQ || op == IR_Const.IR_UNE;
        }
        else if (instruction instanceof Calc_Instruction) {
            String op = ((Calc_Instruction) instruction).get_op_Type();
            return op.equals(IR_Const.ADD) || op.equals(IR_Const.FADD) ||
                    op.equals(IR_Const.MUL) || op.equals(IR_Const.FMUL);
        }
        else {
            return false;
        }

    }
}
